#region Imported Namespaces
using System;
using System.Collections;
using System.Xml;
using System.Xml.Serialization;
using System.Data;
using System.Web;
#endregion

namespace PHSRAG.Insight21.FormsManagement.Serialization
{
	#region Class FormItem
	/// <summary>
	/// FormItem class is a scaled down version of a QuestionnaireItem that can be serialized. The FormItem class also holds the answers for a QuestionnaireItem, if any.
	/// </summary>
	public class FormItem : System.ICloneable
	{
		#region Private Instance Fields
		private string name = string.Empty;
		private string type = string.Empty;
		private string text = string.Empty;
		private string htmlStrippedText = string.Empty;
		private bool hasSeparator;
		private string parentName = string.Empty;
		private string parentValue = string.Empty;
		private bool indent;
		private bool required;
		private bool highlight;

		[NonSerialized]
		private string validationMessage;
		
		private ArrayList options;
		private ArrayList attributes;
		private ArrayList answers;
		private ArrayList formItems;
		#endregion

		#region Properties
		/// <summary>
		/// Gets or sets the name of the question
		/// </summary>
		public string Name
		{
			get { return name; }
			set { name = value; }
		}

		/// <summary>
		/// Gets or sets the type of the question
		/// </summary>
		public string Type
		{
			get { return type; }
			set { type = value; }
		}

		/// <summary>
		/// Gets or sets the question text
		/// </summary>
		public string Text
		{
			get { return text; }
			set { text = value; }
		}

		/// <summary>
		/// Gets or sets the question text without any html tags
		/// </summary>
		public string HtmlStrippedText
		{
			get { return htmlStrippedText; }
			set { htmlStrippedText = value; }
		}

		/// <summary>
		/// Gets or sets whether or not a separator should be displayed before the question
		/// </summary>
		public bool HasSeparator
		{
			get { return hasSeparator; }
			set { hasSeparator = value; }
		}

		/// <summary>
		/// Gets or sets the parent question name, if any
		/// </summary>
		public string ParentName
		{
			get { return parentName; }
			set { parentName = value; }
		}

		/// <summary>
		/// Gets or sets the parent question value, if any
		/// </summary>
		public string ParentValue
		{
			get { return parentValue; }
			set { parentValue = value; }
		}

		/// <summary>
		/// Gets or sets whether or not this question should be indented. Only applicable if the FormItem corresponds to a child question
		/// </summary>
		public bool Indent
		{
			get { return indent; }
			set { indent = value; }
		}

		
		/// <summary>
		/// Holds the collection of FormItemOption objects
		/// </summary>
		[XmlArrayItem(ElementName="ItemOption",	Type=typeof(FormItemOption))]
		[XmlArray(ElementName="ItemOptions")]
		public ArrayList Options
		{
			get { return options; }
			set { options = value; }
		}

		/// <summary>
		/// Holds the collection of FormItemProperty objects
		/// </summary>
		[XmlArrayItem(ElementName="ItemProperty", Type=typeof(FormItemProperty))]
		[XmlArray(ElementName="ItemProperties")]
		public ArrayList Attributes
		{
			get { return attributes; }
			set { attributes = value; }
		}

		/// <summary>
		/// Holds the collection of child FormItem objects(QuestionnaireItems that have other QuestionnaireItems 
		/// based on the answers for the parent QuestionnaireItem). 
		/// 
		/// While converting a Questionnaire into its serializable counterpart(Form), all the QuestionnaireItems 
		/// with an empty parentname are converted to FormItems. Each QuestionnaireItem that depends
		/// on another QuestionnaireItem is converted to a FormItem and is added to the FormItems collection of the
		/// parent FormItem forming a hierarchy of FormItems. 
		/// 
		/// When converting the serialized Form class to html, the child FormItems are enclosed in a non-visible table 
		/// with the table-id set to a concatenated parentquestionname and the parentanswervalue that the child FormItems
		/// require for them to be displayed.
		/// 
		/// For multiselect and dropdownmultiselect questions, the child FormItems are added to the parent FormItem as
		/// described above but their ParentAnswer field is empty. When an answer is added, the child FormItems with empty
		/// ParentAnswer fields are cloned. The ParentAnswer field for the cloned FormItems is set to the new answer added.
		/// This will ensure that all the child formitem values are retained for multiselect and dropdownmultiselect.
		/// While converting the serialized multiselect/dropdownmultiselect to html, only the child form items whose parentanswer 
		/// is set to the selected answer in the list boxes inside multiselect/dropdownmultiselect questions are displayed.
		/// </summary>
		[XmlArrayItem(ElementName="FormItem", Type=typeof(FormItem))]
		[XmlArray(ElementName="FormItems")]
		public ArrayList FormItems
		{
			get { return formItems; }
			set { formItems = value; }
		}

		/// <summary>
		/// Holds the collection of FormAnswer objects
		/// </summary>
		[XmlArrayItem(ElementName="Answer", Type=typeof(FormAnswer))]
		[XmlArray(ElementName="Answers")]
		public ArrayList Answers
		{
			get { return answers; }
			set { answers = value; }
		}

		/// <summary>
		/// Gets or sets whether or not the question is required to be answered
		/// </summary>
		public bool Required
		{
			get { return required; }
			set { required = value; }
		}

		/// <summary>
		/// Gets or sets the value used as a bookmark
		/// 
		/// A question can be bookmarked to let the users know that it needs some attention. 
		/// </summary>
		public bool Highlight
		{
			get { return highlight; }
			set { highlight = value; }
		}

		/// <summary>
		/// Gets or sets the validation message to be dispalyed when the item is not answered
		/// </summary>
		public string ValidationMessage
		{
			get { return validationMessage; }
			set { validationMessage = value; }
		}
		#endregion

		#region Constructors
		/// <summary>
		/// Default Constructor required while serializing the object
		/// </summary>
		public FormItem()
		{
		}
		

		/// <summary>
		/// Builds FormItem from the QuestionnaireItem passed in as a parameter
		/// </summary>
		/// <param name="item">the QuestionnaireItem form which the FormItem needs to be constructed</param>
		public FormItem(QuestionnaireItem item)
		{
			name = item.Name;
			type = item.Type;
			string itemProperty = item.Text;
			text = (itemProperty == null) ? string.Empty : HttpUtility.HtmlDecode(itemProperty);
			htmlStrippedText = HttpUtility.HtmlDecode(item.HtmlStrippedText);
			hasSeparator = item.HasSeparator;
			indent = item.Indent;
			itemProperty = item.ParentName;
			parentName = (itemProperty == null) ? string.Empty : itemProperty;
			itemProperty = item.ParentValue;
			parentValue = (itemProperty == null) ? string.Empty : itemProperty;
			required = item.Required;
			validationMessage = item.ValidationMessage;
			options = new ArrayList();
			foreach(ItemOption o in item.Options)
			{
				options.Add(new FormItemOption(o));
			}
			attributes = new ArrayList();
			foreach(ItemProperty prop in item.Properties.Values)
			{
				attributes.Add(new FormItemProperty(prop));
			}
			formItems = new ArrayList();
			Answers = new ArrayList();
		}

		#endregion

		#region Public methods
		/// <summary>
		/// Unselects all the answers and add the new answer(only Multiselect and dropdownmultiselect)
		/// </summary>
		/// <param name="newItem">The new value that needs to be added to the Answers collection</param>
		/// <param name="id">The id of the Answer(not FormAnswer) object</param>
		public void AddMultiSelectAnswer(string newItem, int id)
		{
			FormAnswer answer = new FormAnswer();
			answer.Answer = newItem;
			answer.Selected = true;
			answer.Id = id;

			foreach(FormAnswer o in answers)
			{
				o.Selected = false;
			}
			answers.Add(answer);

			//clone child formitems with empty parentvalue 
			FormItem childClone;
			FormItem childItem;

			//trackit - 4084
			for(int i=0, count=formItems.Count; i<count; ++i)
			{
				childItem = (FormItem)formItems[i];
				if (childItem.ParentValue == string.Empty)
				{
					childClone = childItem.Clone();
					childClone.ParentValue = newItem;
					formItems.Add(childClone);
				}
			}
		}


		/// <summary>
		/// Removes the FormAnswer object if the answer is a new answer(not saved to database earliet).
		/// Else it sets the deleted property on the FormAnswer being removed. Also removes the corresponding
		/// child FormItem objects. 
		/// Sets the first answer as the selected answer.
		/// </summary>
		/// <param name="removedItem">The answer being removed</param>
		public void RemoveMultiSelectAnswer(string removedItem)
		{

			//If a protocol is unlocked and the form is being modified, then the answers are cloned and id's are set
			//to zero. If the formanswers are removed, then there is no way to reflect those changes back to the actual
			//answers. And hence, formanswers are always marked for deletion.
			foreach(FormAnswer formAnswer in answers)
			{
				if (formAnswer.Answer == removedItem && !formAnswer.Deleted)
				{
					formAnswer.Selected = false;
					formAnswer.Deleted = true;
					break;
				}
			}

//			if (formAnswer.Id == 0)
//				answers.Remove(formAnswer);
//			else
//			{
//				formAnswer.Selected = false;
//				formAnswer.Deleted = true;
//			}
			
			for(int i=formItems.Count-1; i>=0; --i)
			{
				if (((FormItem)formItems[i]).ParentValue == removedItem)
					formItems.RemoveAt(i);
			}
			
			if (answers.Count >0)
			{
				foreach(FormAnswer answer in answers)
				{
					if (!answer.Deleted)
					{
						answer.Selected = true;
						break;
					}
				}
			}
		}
		
		
		/// <summary>
		/// Sets the specified answer as selected
		/// </summary>
		/// <param name="selectedItem">The item selected by the user</param>
		public void SelectMultiSelectAnswer(string selectedItem)
		{
			foreach(FormAnswer formAnswer in answers)
			{
				formAnswer.Selected = (!formAnswer.Deleted && formAnswer.Answer == selectedItem);
			}
		}


		/// <summary>
		/// Adds the answer to the question. If a different answer already exists then it is marked for deletion.
		/// This method should only be used for questions that have only one answer.
		/// </summary>
		/// <param name="newValue">The answer for the question</param>
		/// <param name="id">The id of the answer being added. For new answers, this parameter should be set to 0</param>
		public void AddOrReplaceAnswer(string newValue, int id)
		{
			//There are 3 cases.
			//1. No answer
			//2. One answer (this could already be saved to the database)
			//3. Two answers - One saved to database but marked as deleted and the other is the modified answer 
			bool answerExists = false;
			FormAnswer answer;
			if (answers.Count >= 1)
			{
				int i=0;
				int j = answers.Count;
				//loop thru the answers and search if one with the 'newValue' exists. if found, set the flags. 
				//if answers that do not match the 'newValue' exist, then either mark them for deletion or delete them based on their id's.
				do
				{
					answer = (FormAnswer)answers[i];
//					If a protocol is unlocked and the form is being modified, then the answers are cloned and id's are set
//					to zero. If the formanswers are removed, then there is no way to reflect those changes back to the actual
//					answers. And hence, formanswers are always marked for deletion.
//					if (answer.Id != 0)
//					{
						if (answer.Answer == newValue)
						{
							answer.Deleted = false;
							answer.Selected = true;
							answerExists = true;
							++i;
						}
						else
						{
							answer.Deleted = true;
							answer.Selected = false;
							++i;
						}
//					}
//					else
//					{
//						if (answer.Answer == newValue)
//						{
//							//In this case, no need to set deleted flag to false and selected flag to true because if an answer exists that is
//							//not yet saved to the database and the user selects another answer, then former would be deleted instead of being
//							//marked for deletion. Look at the following else case.
//							answerExists = true;
//							++i;
//						}
//						else
//						{
//							//This answer is not yet saved to database. So, delete it
//							answers.RemoveAt(i);
//							--j;
//						}
//					}
				}
				while(i<j);
								
			}
			if (!answerExists)
			{
				answer = new FormAnswer();
				answer.Id = id;
				answer.Selected = true;
				answer.Answer =newValue;
				answers.Add(answer);
			}
		}


		/// <summary>
		/// Marks all answers as deleted. 
		/// </summary>
		public void DeleteAnswer()
		{
			foreach (FormAnswer answer in answers)
			{
				answer.Deleted = true;
				answer.Selected = false;
			}
		}


		/// <summary>
		/// Adds a checkbox answer if not previously added. If the checkbox answer was previously added, then it sets the deleted property to false.
		/// </summary>
		/// <param name="optionValue">The selected option's value</param>
		/// <param name="id">The id of the answer being added. For new answers, this parameter must be set to 0</param>
		public void AddCheckBoxAnswer(string optionValue, int id)
		{
			FormAnswer answer = null;
			foreach(Object o in answers)
			{
				answer = (FormAnswer)o;
				if (answer.Answer == optionValue)
				{
					answer.Deleted = false;
					return;
				}
			}
			//if answer is not found...
			answer = new FormAnswer();
			answer.Selected = true;
			answer.Answer = optionValue;
			answer.Id = id;
			answers.Add(answer);
			
		}
		
		
		/// <summary>
		/// Removes a checkbox answer when the option is unselected
		/// </summary>
		/// <param name="optionValue">The option to be removed from the answers collection</param>
		public void RemoveCheckBoxAnswer(string optionValue)
		{
			FormAnswer answer;
			for(int i=answers.Count-1; i>=0; --i)
			{
				answer = (FormAnswer)answers[i];
				if (answer.Answer == optionValue)
				{
//					If a protocol is unlocked and the form is being modified, then the answers are cloned and id's are set
//					to zero. If the formanswers are removed, then there is no way to reflect those changes back to the actual
//					answers. And hence, formanswers are always marked for deletion.
//					if (answer.Id == 0)
//						answers.RemoveAt(i);
//					else
						answer.Deleted = true;
					break;
				}
			}
		}

		/// <summary>
		/// Removes the answer. The question is assumed to have only one answer.
		/// </summary>
		public void RemoveAnswer()
		{
			if (answers.Count > 0)
			{
				FormAnswer answer = (FormAnswer)answers[0];
				if (answer.Id == 0)
					answers.Clear();
				else
					answer.Deleted = true;
			}
		}

		#endregion

		#region ICloneable Members
		/// <summary>
		/// Explicit interface method implementation - available for 
		/// clients of ICloneable, but invisible to other
		/// clients of FormItem class
		/// </summary>
		/// <returns>FormItem clone as a generic object</returns>
		object ICloneable.Clone()
		{
			return Clone();
		}

		
		/// <summary>
		/// Deep Copies the FormItem object
		/// </summary>
		/// <returns>A clone of the current FormItem</returns>
		public FormItem Clone()
		{
			FormItem clone = (FormItem)MemberwiseClone();
			
			clone.Options = new ArrayList();
			foreach(FormItemOption o in options)
			{
				clone.Options.Add(o.Clone());
			}
			
			clone.Attributes = new ArrayList();
			foreach(FormItemProperty prop in attributes)
			{
				clone.Attributes.Add(prop.Clone());
			}

			clone.FormItems = new ArrayList();
			foreach(FormItem itm in formItems)
			{
				clone.FormItems.Add(itm.Clone());
			}

			clone.Answers = new ArrayList();
			foreach(FormAnswer ans in answers)
			{
				clone.Answers.Add(ans.Clone());
			}
			return clone;
		}
		#endregion
	}
	#endregion
}
